/**
 * 基础库
 */
import './polyfill'
import WatchRoute from 'watch-route'
import { version } from '../package.json'

const GT = key => window.sessionStorage.getItem(key)
const ST = (key, value) => window.sessionStorage.setItem(key, value)

const GT_O = key => JSON.parse(GT(key) || '{}')
const ST_O = (key, value) => ST(key, JSON.stringify(value))

// 页面上报纪录缓存
const getPageReport = () => GT_O('SXYJ_STAT_PAGE')
const setPageReport = value => ST_O('SXYJ_STAT_PAGE', value)

// 节点上报纪录缓存
const getNodeReport = path => GT_O('SXYJ_STAT_NODE' + '-' + path)
const setNodeReport = (path, value) => ST_O('SXYJ_STAT_NODE' + '-' + path, value)

/**
 * @method Stat
 * @desc 埋点上报
 * @param {Function} report -> 接受上报参数的函数
 * @param {Object} params
 * @param {Number} params.chainLength -> 页面链长度
 * @param {Boolean} params.oncePageView -> 在此次浏览一个页面只上报一次
 * @param {Boolean} params.onceNodeView -> 在此次浏览一个节点只上报一次
 * @param {Function} params.startCallback -> 将 watch-route 中 首次执行添加页面 的方法传入外部，由外部异步调用
 */
class Stat {
	constructor(report, { 
		chainLength = 6,
		oncePageView = true,
		onceNodeView = true,
		startCallback = null,
	} = {}) {
		// 配置项
		this._options = {
			chainLength,
			oncePageView,
			onceNodeView,
			// 用于存储dom的key，在页面跳转时由外部改变
			DOM_toKey: '',
			DOM_fromKey: '',
		}
		// 上报回调，统一返回数组
		this._report = (data, options) => Array.isArray(data) ? this.options.report(data, options) : this.options.report([data], options)
		// 路由实例
		this._route = new WatchRoute({ chainLength, getLastPageDetail: true, startCallback })
		// 存储dom的key，根页面
		this._currentKey = '__DEFAULT_KEY__'
		// 节点存储列表
		this._domNodeList = {}
		// 节点点击事件存储列表
		this._domClickList = {}
		// cachePageReport 为 true 时的 页面缓存列表
		this._cachePage = []
		// cachePageReport 为 true 时的 节点缓存列表
		this._cacheNode = []
		// 页面上报历史纪录列表
		this._pageReportHistory = getPageReport()
		// 页面节点展示上报历史纪录列表
		this._nodeReportHistory = {}


		// 用于外部修改的配置项
		this.options = {
			// 上报方法
			report,
			// 停止上报
			stopPageReport: false,
			// 停止并存储上报数据，以便再次启动时获取数据
			cachePageReport: report ? false : true,
			// 停止路由页面上报
			stopRouteReport: false,
			// 基础数据的默认值
			baseDataDefault: {},
			// 其他上报的默认数据
			otherDataDefault: {},
			// 在 otherDataDefault 中，需要执行的 key，为了获取异步数据
			// runKey: [],
			// 页面上报默认pathname
			reportPathname: '',
		}

		// 初始化
		this.init()
	}

	/**
	 * @method setReport
	 * @description 异步设置上报方法
	 * @param {Function} report 上报方法
	 */
	setReport(report) {
		if (typeof report !== 'function') return

		// 设置上报
		this.options.report = report
		// 将缓存数据上报
		this.options.cachePageReport = false
		this.getCachePage().forEach(item => this._report(item))
		this.getCacheNode().forEach(item => this._report(item))
	}

	/**
	 * @method bindDom
	 * @description 绑定节点
	 * @param {NODE} el 需要绑定的dom节点 
	 * @param {Object} param1
	 * @param {String} param1.id 该节点在当前页面的唯一键值
	 * @param {Boolean} param1.unBind 该节点不需要绑定
	 * @param {Boolean} param1.unClick 该节点不需要点击上报
	 * @param {Object} params 需要绑定的数据
	 */
	bindDom(el, { id = '', unBind = false, unClick = false, ...params } = {}, path) {
		if (!id || unBind) return
		const key = path || this._currentKey

		// vue中已有数据的指令执行速度优先于 _route.afterEach ，于是在这里做赋值
		if (!this._domNodeList[key]) {
			this._domNodeList[key] = {}
			this._domClickList[key] = {}
		}

		this._domNodeList[key][id] = { el, params }

		if ( !unClick ) {
			if (this._domClickList[key][id]) el.removeEventListener('click', this._domClickList[key][id])

			// 保存点击事件，便于移除事件
			this._domClickList[key][id] = e => this.clickReport(params)
			el.addEventListener('click', this._domClickList[key][id])
		}
	}

	/**
	 * @method removeBind
	 * @description 解除绑定
	 * @param {NODE} el 绑定的dom节点
	 * @param {Object} param1
	 * @param {String} param1.id 该节点在当前页面的唯一键值
	 * @param {Boolean} param1.unBind 该节点不需要绑定
	 * @param {Boolean} param1.unClick 该节点不需要点击上报
	 */
	removeBind(el, { id = '', unBind = false, unClick = false } = {}) {
		if (!id || unBind) return
		const key = this._currentKey

		!unClick && el.removeEventListener('click', this._domClickList[key][id])
		delete this._domClickList[key][id]
		delete this._domNodeList[key][id]
	}

	/**
	 * @method getCachePage
	 * @description 获取缓存的页面，获取一次后默认清除全部
	 * @return 缓存的页面
	 */
	getCachePage(from = 0, to) {

		return this._cachePage.splice(from, this._cachePage.slice(from, to).length)
	}
	/**
	 * @method getCacheNode
	 * @description 获取缓存的节点，获取一次后默认清除全部
	 * @return 缓存的节点
	 */
	getCacheNode(from = 0, to) {

		return this._cacheNode.splice(from, this._cacheNode.slice(from, to).length)
	}

	/**
	 * @method clickReport
	 * @description 点击上报
	 * @param {Object} params 上报参数
	 */
	clickReport(params, other = {}) {
		return this._report(this._getNodeClickData(params), other)
	}

	/**
	 * @method viewReport
	 * @description 视图上报
	 * @param {Object | Array} params 上报参数，当来源为node时，该参数为数组
	 * @param {Object} params1 
	 * @param {String} params1.pathname 路径，由于afterEach是延时上报，传入具有确定性
	 * @param {String} params1.target 上报来源
	 */
	viewReport(params, 
		{ 
			pathname = this.options.reportPathname || (this._route.currentPage.pathname + this._route.currentPage.search), 
			target = 'page',
			...other,
		} = {}) {
		// 停止上报
		if (this.options.stopPageReport) return

		switch (target) {
			case 'page':
				// 上报缓存
				if (this.options.cachePageReport) return this._cachePage.push(this._getPageViewData(params))
			
				// 缓存页面纪录
				if (this._options.oncePageView) {
					if (this._pageReportHistory[pathname]) return
					this._pageReportHistory[pathname] = true
					setPageReport(this._pageReportHistory)
				}

				// 页面展示上报
				return this._report(this._getPageViewData(params), other)
			case 'node':
				// 上报缓存
				if (this.options.cachePageReport) return this._cacheNode.push(this._getNodeViewData(params))

				// 节点展示上报
				return this._report(this._getNodeViewData(params), other)
		
			default: return
		}
	}

	/**
	 * @method nodeReport
	 * @description 节点上报
	 * @param {String} params 其他参数
	 */
	nodeReport(param = {}) {
		const key = this._currentKey
		
		// 再次确保当前历史记录引用存在
		if (!this._nodeReportHistory[key]) this._nodeReportHistory[key] = this._options.onceNodeView ? getNodeReport(key) : {}
		
		const ids = Object.keys(this._domNodeList[key] || {})
		const { innerHeight, innerWidth } = window
		// 需要上报的列表
		const reportList = []

		ids.forEach(id => {
			// 已上传过
			if (!this._domNodeList[key][id]) return
			if (this._nodeReportHistory[key][id]) return delete this._domNodeList[key][id]

			const { el, params } = this._domNodeList[key][id]
			const { left, top } = el.getBoundingClientRect()
			const { offsetWidth, offsetHeight } = el
			
			if ( 
				// 在窗口内
				left >= 0 - offsetWidth / 3 * 2 &&
				left <= innerWidth - offsetWidth / 3 &&
				top >= 0 - offsetHeight / 3 * 2 &&
				top <= innerHeight - offsetHeight / 3
			) {
				reportList.push(params)
				delete this._domNodeList[key][id]
				this._nodeReportHistory[key][id] = true
			}
		})
		// 节点纪录展示缓存
		this._options.onceNodeView && setNodeReport(key, this._nodeReportHistory[key])

		reportList.length && this.viewReport(reportList, { target: 'node', ...param })
	}

	/**
	 * @method _getBaseData
	 * @description 获取基础上报数据
	 * @param {Object} params 
	 * @param {String} params.type 行为类型
	 * @param {String} params.target 来源
	 * @param {String} params.page 当前页面
	 * @param {String} params.parentPage 父级页面
	 * @param {Object} params.pageParams 当前页面参数
	 * @param {Object} params.actionParams 行为参数
	 */
	_getBaseData({ 	
		type = this.options.baseDataDefault.type || 'view',
		target = this.options.baseDataDefault.target || '页面',
		page = this.options.baseDataDefault.page || this._route.currentPage.href,
		parentPage = this.options.baseDataDefault.parentPage || this._route.parentPage.href || '',
		pageParams = this.options.baseDataDefault.pageParams || { ...this._route.currentPage.query },
		parentParams = this.options.baseDataDefault.parentParams || { ...this._route.parentPage.query },
		actionParams = this.options.baseDataDefault.actionParams || {},
		...other
	} = {}) {
		const { otherDataDefault } = this.options
		const otherData = { ...otherDataDefault }
		
		// this.options.runKey.forEach(key => {
		Object.keys(otherDataDefault).forEach(key => {
			const value = typeof otherDataDefault[key] === 'function' ? otherDataDefault[key]() : otherDataDefault[key]
			if (otherDataDefault[key] !== void 0) otherData[key] = value
		})

		return {
			version,

			type,
			target,
			page,
			parentPage,
			pageParams,
			parentParams,
			actionParams,

			pageDetail: {
				pageStack: this._route.getOwnPageStack(),
				chain: this._route.getOwnChain(),
				time: Date.now() + '',
				pageSize: `${window.innerWidth}x${window.innerHeight}`,
				title: window.document.title,
			},
			other: {
				...other,
				...otherData,
			},
		}
	}

	/**
	 * @method _getPageViewData
	 * @description 获取页面上报数据
	 * @param {Object} params 其他参数
	 * @return 页面上报数据列表
	 */
	_getPageViewData(params) {

		return [this._getBaseData({
			type: 'view',
			...params
		})]
	}

	/**
	 * @method _getNodeClickData
	 * @description 获取点击上报的数据
	 * @param {Object} params 其他参数
	 * @return 点击上报数据列表
	 */
	_getNodeClickData(params) {

		return [this._getBaseData({
			type: 'click',
			...params
		})]
	}

	/**
	 * @method _getNodeViewData
	 * @description 获取页面上报的数据
	 * @param {Array} params 其他参数
	 * @return 节点上报数据列表
	 */
	_getNodeViewData(params) {

		return params.map(item => this._getBaseData({
			type: 'view',
			...item
		}))
	}

	/**
	 * @method init
	 * @description 初始化
	 */
	init() {
		// 页面上报防抖
		this._route.afterEach(route => {
			// 变更key
			const pathname = route.detail.pathname
			const toKey = this._currentKey = this._options.DOM_toKey || pathname.match(/^\/?[^\/?#]*/)[0]

			const lastPathname = route.lastChainDetail.pathname || ''
			const fromKey = this._options.DOM_fromKey || lastPathname.match(/^\/?[^\/?#]*/)[0]

			// 根页面改变，一般意味着节点完全改变
			if (toKey !== fromKey) {
				if (!this._domNodeList[toKey]) {
					this._domNodeList[toKey] = {}
					this._domClickList[toKey] = {}
				}

				this._domNodeList[fromKey] = {}
				this._domClickList[fromKey] = {}
			}

			// 节点历史纪录
			if (!this._nodeReportHistory[toKey]) this._nodeReportHistory[toKey] = this._options.onceNodeView ? getNodeReport(toKey) : {}
			
			// 停止页面跳转上报
			if (this.options.stopRouteReport) return
			// 页面跳转上报
			setTimeout(() => this.viewReport({}, { pathname: pathname + route.detail.search }), 4)
		})

		// 页面滚动上报节点防抖
		let nodeTimer

		window.addEventListener('scroll', e => {
			clearTimeout(nodeTimer)
	
			nodeTimer = setTimeout(this.nodeReport.bind(this), 500)
		})
	}
}


let stat
export default function(...arg) { return stat || (stat = new Stat(...arg)) }